/******************************************************************************
 alm3gps.c for TTY to socket server

 Almogetracker receives data from a GPS receiver and transmits it
 over amature radio using the APRS protocol version 1.0.

 This module transfer GPS data from UART to a socket that can be accessed
 via the networkport.

 Copyright (C)2013, Andreas Kingbäck  (andki234@gmail.com)

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 2 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/

/**
 * @file alm3gps.c
 * GPS Socket Server
 * @brief This section contains code for the TTY to socket server
 *
 * @author Andreas Kingbäck
 * @version 1.00
 */

//-------------------------------------------------------------------------------------------------
//--------  I N C L U D E -------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
#include <stdbool.h>
#include <stdint.h>
#include <sys/signal.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <termios.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include "alm3gps.h"

//-------------------------------------------------------------------------------------------------
//--------  G L O B A L  V A R I A B L E S  -----------------------------------------------
//-------------------------------------------------------------------------------------------------
static int tty_fd;
static int listen_sd;
struct sigaction saio;
struct termios tios;
struct sockaddr_in in_sock_addr;

//-------------------------------------------------------------------------------------------------
//--------  F U N C T I O N S  --------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void SOCKET__Init(void)
{
    int status;

    /* Create an AF_INET stream socket to receive */
    /* incoming connections on */

    listen_sd = socket(AF_INET, SOCK_STREAM, 0);

    if (listen_sd < 0)
    {
        perror("SOCKET - socket() error");

    }
    else
    {
        printf("SOCKET - socket() is OK\n");
    }

    fcntl(listen_sd, F_SETFL, O_NONBLOCK);

    /* Bind the socket */

    memset(&in_sock_addr, 0, sizeof(in_sock_addr));

    in_sock_addr.sin_family = AF_INET;
    in_sock_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    in_sock_addr.sin_port = htons(SERVER_PORT);

    status = bind(listen_sd, (struct sockaddr *) &in_sock_addr, sizeof(in_sock_addr));

    if (status < 0)
    {
        perror("SOCKET - bind() error");
        close(listen_sd);
    }
    else
    {
        printf("SOCKET - bind() is OK\n");
    }

    /* Set the listen backlog */
    status = listen(listen_sd, 50);

    if (status < 0)
    {
        perror("SOCKET - listen() error");
        close(listen_sd);

    }
    else
    {
        printf("SOCKET - listen() is OK\n");
    }

    /* Inform the user that the server is ready */
    printf("SOCKET - server is ready!\n");
}

void SOCKET__Close(void)
{
    close(listen_sd);

    printf("SOCKET - close() is OK\n");
}

int TTY__Init(void)
{
    int status;

    char line[1024];

    tty_fd = open(GPSDEVICE, O_RDONLY | O_NOCTTY);

    if (tty_fd < 0)
    {
        printf("Can't open /dev/ttyO1 device file\n");

        perror(GPSDEVICE);

        status = -1;
    }
    else
    {
        bzero(&tios, sizeof(tios)); /* clear struct for new port settings */

        /*
         BAUDRATE: Set bps rate. You could also use cfsetispeed and cfsetospeed.
         CRTSCTS : output hardware flow control (only used if the cable has
         all necessary lines. See sect. 7 of Serial-HOWTO)
         CS8     : 8n1 (8bit,no parity,1 stopbit)
         CLOCAL  : local connection, no modem contol
         CREAD   : enable receiving characters
         */

        tios.c_cflag = CRTSCTS | CS8 | CLOCAL | CREAD;

        /*
         TXBAUDRATE: Set transmit bps rate.
         RXBAUDRATE: Set receive bps rate.
         */

        cfsetospeed(&tios, TXBAUDRATE);
        cfsetispeed(&tios, RXBAUDRATE);

        /*
         IGNPAR  : ignore bytes with parity errors
         ICRNL   : map CR to NL (otherwise a CR input on the other computer
         will not terminate input)
         otherwise make device raw (no other input processing)
         */
        tios.c_iflag = IGNPAR | ICRNL;

        /*
         Raw output.
         */
        tios.c_oflag = 0;

        /*
         ICANON  : enable canonical input
         disable all echo functionality, and don't send signals to calling program
         */
        tios.c_lflag = ICANON;

        /*
         initialize all control characters
         default values can be found in /usr/include/termios.h, and are given
         in the comments, but we don't need them here
         */
        tios.c_cc[VINTR] = 0; /* Ctrl-c */
        tios.c_cc[VQUIT] = 0; /* Ctrl-\ */
        tios.c_cc[VERASE] = 0; /* del */
        tios.c_cc[VKILL] = 0; /* @ */
        tios.c_cc[VEOF] = 4; /* Ctrl-d */
        tios.c_cc[VTIME] = 0; /* inter-character timer unused */
        tios.c_cc[VMIN] = 0; /* blocking read until 1 character arrives */
        tios.c_cc[VSWTC] = 0; /* '\0' */
        tios.c_cc[VSTART] = 0; /* Ctrl-q */
        tios.c_cc[VSTOP] = 0; /* Ctrl-s */
        tios.c_cc[VSUSP] = 0; /* Ctrl-z */
        tios.c_cc[VEOL] = 0x0D; /* '\0' */
        tios.c_cc[VREPRINT] = 0; /* Ctrl-r */
        tios.c_cc[VDISCARD] = 0; /* Ctrl-u */
        tios.c_cc[VWERASE] = 0; /* Ctrl-w */
        tios.c_cc[VLNEXT] = 0; /* Ctrl-v */
        tios.c_cc[VEOL2] = 0; /* '\0' */

        /*
         now clean the modem line and activate the settings for the port
         */
        tcflush(tty_fd, TCIFLUSH);
        tcsetattr(tty_fd, TCSANOW, &tios);

        status = 1;
    }

    return status;
}

void TTY__Close(void)
{
    close(tty_fd);

    printf("GPSTTY - close() is OK\n");
}

int TTY__Read(char *buf, int length)
{
    int rlen;

    memset(buf, 0, length);

    rlen = read(tty_fd, buf, length);

    buf[length] = 0x0D;

    return rlen;
}

int Alm3GPS__PrintfTime()
{
    time_t rawtime;
    struct tm * ptm;

    time(&rawtime);

    ptm = gmtime(&rawtime);

    printf("%2d:%02d: ", ptm->tm_hour % 24, ptm->tm_min);
}

void Alm3GPS__SetSystemDateTime()
{
    system("date 0820102508.30");
    printf("Date changed!\n");
}

void Alm3GPS__Server(void)
{
    fd_set master_fd;
    fd_set read_fds;

    int i, j;
    int max_fd, new_fd;
    int sel;
    int status;
    int addrlen;
    int nof_bytes_received;

    struct timeval waitd;
    struct sockaddr_in clientaddr;

    char gps_buffer[200];
    char rx_buffer[100];

    FD_ZERO(&master_fd);
    FD_SET(listen_sd, &master_fd); // add the listener to the master set

    max_fd = listen_sd; // Set max file decriptor to listen_sd
    read_fds = master_fd; // copy it

    while (1 == 1)
    {
        usleep(100);

        fflush(stdout);

        waitd.tv_sec = 1;

        read_fds = master_fd;

        sel = select(max_fd + 1, &read_fds, NULL, NULL, &waitd);

        if (sel == -1)
        {
            perror("SOCKET - Select() error");
        }
        else
        {
            for (i = 0; i <= max_fd; i++)
            {
                if (FD_ISSET(i, &read_fds))
                {
                    if (i == listen_sd)
                    {
                        //handle new connections
                        addrlen = sizeof(clientaddr);

                        if ((new_fd = accept(listen_sd, (struct sockaddr *) &clientaddr, &addrlen)) == -1)
                        {
                            perror("SOCKET - Server-accept() error");
                        }
                        else
                        {
                            printf("SOCKET - Server-accept() is OK...\n");

                            FD_SET(new_fd, &master_fd); // add to master set

                            if (new_fd > max_fd)
                            {
                                max_fd = new_fd;
                            }

                            Alm3GPS__PrintfTime();
                            printf("SOCKET - New connection from %s on socket %d\n", inet_ntoa(clientaddr.sin_addr), new_fd);
                        }
                    }
                }

                nof_bytes_received = TTY__Read(gps_buffer, sizeof(gps_buffer));

                if (nof_bytes_received > 1)
                {
                    for (j = 0; j <= max_fd; j++)
                    {
                        if (FD_ISSET(j, &master_fd) && (j != listen_sd))
                        {
                            status = send(j, gps_buffer, nof_bytes_received, MSG_NOSIGNAL);

                            if (status == -1)
                            {
                                printf("SOCKET - Socket %d hung up\n", j);

                                FD_CLR(j, &master_fd);
                                close(j);
                            }
                        }
                    }
                }
            }
        }
    }
}

int main(void)
{
    TTY__Init();
    SOCKET__Init();
    Alm3GPS__Server();
    SOCKET__Close();
    TTY__Close();
}
